/**
 * 特殊值转换：
 *      ${prechecklist|dicCodeField|dicCodeValueField}
 *          以checkbox形式展示所有的字典，选中时打勾；
 *          dicCodeField：字典来源哪个字典
 *          dicCodeValueKey：字典选中值来源哪个字段，级联取值以.分割
 *      ${checklist|dicCode|dicCodeValueField}
 *          以checkbox形式展示所有的字典，选中时打勾；
 *          dicCode：来源哪个字典
 *          dicCodeValueKey：字典选中值来源哪个字段，级联取值以.分割
 *      ${dict|dicCode|dicCodeValueField}
 *          将字典转换成字典值；
 *          dicCode：来源哪个字典
 *          dicCodeValueKey：字典选中值来源哪个字段，级联取值以.分割
 *      ${loopRow|valueField}
 *          当前行为循环行；
 *          valueField：循环list来源于哪个字段，级联取值以.分割
 */
package com.slipper.common.word;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import javax.servlet.http.HttpServletResponse;

import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTPPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTRPr;
import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTTcPr;

import com.slipper.common.util.AttachUtil;
import com.slipper.common.util.BeanUtil;
import com.slipper.common.util.DateUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WordUtil {
    private static final Logger logger = LoggerFactory.getLogger(WordUtil.class);

    public static final String NON_VALUE = "__NON_VALUE__";
    
    /**
     * 导出word，只支持1个1-2级循环
     * @param tplName 模板名称
     * @param data 数据列
     * @param fileName 文件名
     * @param dicsMap 字典
     */
    public static void export(
            HttpServletResponse response,
            String tplName, 
            Object data,
            String fileName,
            Map<String, Map<String, String>> dicsMap) throws Exception {
        InputStream inputStream = AttachUtil.getTplInputStream(tplName);
        export(response, inputStream, -1, 0, data, null, fileName, dicsMap);
    }

    /**
     * 导出word，只支持1个1-2级循环
     * @param inputStream 输入流
     * @param data 数据列
     * @param fileName 文件名
     * @param dicsMap 字典
     */
    public static void export(
            HttpServletResponse response,
            InputStream inputStream, 
            Object data,
            String fileName,
            Map<String, Map<String, String>> dicsMap) throws Exception {
        export(response, inputStream, -1, 0, data, null, fileName, dicsMap);
    }
    
    /**
     * 导出word，只支持1个1-2级循环
     * @param inputStream 输入流
     * @param tableIndex 循环列表是第几个表格
     * @param startIndex 循环列表表格开始行
     * @param data 数据列
     * @param loopVOs 列表配置
     * @param fileName 文件名
     * @param dicsMap 字典
     */
    public static void export(
            HttpServletResponse response,
            InputStream inputStream, 
            int tableIndex, 
            int startIndex,
            Object data,
            LoopVO[] loopVOs,
            String fileName,
            Map<String, Map<String, String>> dicsMap) throws Exception {
        response.reset();
        if (fileName == null || fileName.trim().isEmpty()) {
            fileName = "default.xlsx"; // 定义保存的文件名字
        }
        response.setContentType("application/octet-stream");
        String trueName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
        response.setHeader("Content-Disposition", "attachment; filename=\"" + trueName + "\"");

        OutputStream outputStream = response.getOutputStream();
        XWPFDocument xwpfDocument = new XWPFDocument(inputStream);

        // 查找标签是否包含有循环表格
        if (tableIndex == -1) {
            LoopVO loopVO = lookLoop(xwpfDocument);
            if (loopVO != null) {
                tableIndex = loopVO.getTableIndex();
                startIndex = loopVO.getStartRow() + 1;
                loopVOs = new LoopVO[]{loopVO};
            }
        }

        // 填充循环表格
        parserTable(xwpfDocument, tableIndex, startIndex, data, loopVOs, dicsMap);

        // 填充其它的标签
        replaceSS(xwpfDocument, data, dicsMap);
        xwpfDocument.write(outputStream);
        xwpfDocument.close();
        close(inputStream);

    }

    public static void export(
            HttpServletResponse response,
            String tplName,
            int tableIndex,
            int startIndex,
            Object data,
            LoopVO[] loopVOs,
            String fileName,
            Map<String, Map<String, String>> dicsMap) throws Exception {
        response.reset();
        if (fileName == null || fileName.trim().isEmpty()) {
            fileName = "default.xlsx"; // 定义保存的文件名字
        }
        response.setContentType("application/octet-stream");
        String trueName = new String(fileName.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
        response.setHeader("Content-Disposition", "attachment; filename=\"" + trueName + "\"");
        if(data instanceof List){
            ZipOutputStream zipOutputStream=new ZipOutputStream(response.getOutputStream());
            List<?> list=(List<?>) data;
            for(int i=0;i<list.size();i++){
                InputStream inputStream = AttachUtil.getTplInputStream(tplName);
                XWPFDocument xwpfDocument = new XWPFDocument(inputStream);
                // 查找标签是否包含有循环表格
                if (tableIndex == -1) {
                    LoopVO loopVO = lookLoop(xwpfDocument);
                    if (loopVO != null) {
                        tableIndex = loopVO.getTableIndex();
                        startIndex = loopVO.getStartRow() + 1;
                        loopVOs = new LoopVO[]{loopVO};
                    }
                }
                // 填充循环表格
                parserTable(xwpfDocument, tableIndex, startIndex, list.get(i), loopVOs, dicsMap);
                // 填充其它的标签
                replaceSS(xwpfDocument, list.get(i), dicsMap);

                String name=BeanUtil.getValue(list.get(i),"name").toString();
                String exportTime=BeanUtil.getValue(list.get(i),"exportTime").toString();
                zipOutputStream.putNextEntry(new ZipEntry(name+"_"+exportTime+"_"+i+".docx"));
                xwpfDocument.write(zipOutputStream);
                zipOutputStream.closeEntry();
                xwpfDocument.close();
                inputStream.close();
            }
            zipOutputStream.close();
        }
    }

    /**
     * 查找表格中是否存在循环列表
     */
    private static LoopVO lookLoop(XWPFDocument xwpfDocument) {
        List<XWPFTable> tableList = xwpfDocument.getTables();
        if (tableList == null || tableList.isEmpty()) {
            return null;
        }
        
        int tableIndex = 0;
        int rowIndex = 0;
        for (XWPFTable table : tableList) {
            for (XWPFTableRow row : table.getRows()) {
                for (XWPFTableCell cell : row.getTableCells()) {
                    for (XWPFParagraph para : cell.getParagraphs()) {
                        for (XWPFRun run : para.getRuns()) {
                            String text = run.toString();
                            // ${loopRow|valueField}
                            Matcher matcher = bookmarkMatcher(text);
                            while (matcher.find()) {
                                String bookmark = matcher.group(1);
                                if (bookmark.indexOf("loopRow") == 0) {
                                    LoopVO loopVO = new LoopVO();
                                    loopVO.setTableIndex(tableIndex);
                                    loopVO.setStartRow(rowIndex);
                                    loopVO.setEndRow(rowIndex);
                                    loopVO.setListProp(bookmark.split("\\|")[1]);
                                    return loopVO;
                                }
                            }
                        }
                    }
                }
                
                rowIndex++;
            }

            tableIndex++;
            rowIndex = 0;
        }
        
        return null;
    }

    /**
     * 循环填充表格内容
     */
    private static void parserTable(
            XWPFDocument xwpfDocument, 
            int tableIndex, 
            int startIndex,
            Object data,
            LoopVO[] loopVOs,
            Map<String, Map<String, String>> dicsMap) throws Exception {
        if (loopVOs == null) {
            return;
        }
        
        List<XWPFTable> tableList = xwpfDocument.getTables();
        if (tableList.size() <= tableIndex) {
            throw new Exception("tableIndex对应的表格不存在");
        }
        
        XWPFTable table = tableList.get(tableIndex);
        List<XWPFTableRow> rows = table.getRows();
        if (rows.size() < 2) {
            throw new Exception("tableIndex对应表格应该为2行");
        }
        
        parseTableList(table, rows, startIndex, data, loopVOs, dicsMap);
        
        // 删除模版行
        if (loopVOs.length > 0) {
            int startRow = Arrays.stream(loopVOs).mapToInt(LoopVO::getStartRow).min().getAsInt();
            int endRow = Arrays.stream(loopVOs).mapToInt(LoopVO::getEndRow).max().getAsInt();
            for (int i=startRow;i<=endRow;i++) {
                table.removeRow(startRow);
            }
        }
    }
    
    private static int parseTableList(
            XWPFTable table, 
            List<XWPFTableRow> rows, 
            int index, 
            Object data, 
            LoopVO[] loopVOs,
            Map<String, Map<String, String>> dicsMap) {
        if (loopVOs == null || loopVOs.length <= 0) {
            return index;
        }
        
        LoopVO loopVO = loopVOs[0];
        List<Object> dataList = (List<Object>) BeanUtil.getValue(data, loopVO.getListProp());
        if (loopVOs.length > 1) {
            loopVOs = Arrays.copyOfRange(loopVOs, 1, loopVOs.length);
        } else {
            loopVOs = null;
        }
        if (dataList != null && !dataList.isEmpty()) {
            for (Object tempData : dataList) {
                index = parseTable(table, rows, index, tempData, loopVO, loopVOs, dicsMap);
            }
        }
        return index;
    }
    
    /**
     * 解释循环表格
     * @param table 表格
     * @param index 插入位置
     * @param data 数据
     */
    private static int parseTable(
            XWPFTable table, 
            List<XWPFTableRow> rows, 
            int index,
            Object data, 
            LoopVO loopVO,
            LoopVO[] loopVOs,
            Map<String, Map<String, String>> dicsMap) {
        for (int i = loopVO.getStartRow(); i <= loopVO.getEndRow(); i++) {
            XWPFTableRow sourceRow = rows.get(i);
            XWPFTableRow targetRow = table.insertNewTableRow(index);
            copyTableRow(targetRow, sourceRow, data, dicsMap);
            index++;
        }
        
        return parseTableList(table, rows, index, data, loopVOs, dicsMap);
    }
    
    /**
     * 功能描述:复制行，从source到target
     */
    private static void copyTableRow(
            XWPFTableRow target, 
            XWPFTableRow source, 
            Object data,
            Map<String, Map<String, String>> dicsMap) {
        // 复制样式
        if (source.getCtRow() != null) {
            target.getCtRow().setTrPr(source.getCtRow().getTrPr());
        }
        // 复制单元格
        for (int i = 0; i < source.getTableCells().size(); i++) {
            XWPFTableCell targetCell = target.getCell(i);
            XWPFTableCell sourceCell = source.getCell(i);
            if (targetCell == null) {
                targetCell = target.addNewTableCell();
            }
            copyTableCell(targetCell, sourceCell, data, dicsMap);
        }
    }
    
    /**
     * 功能描述:复制单元格，从source到target
     */
    private static void copyTableCell(
            XWPFTableCell target, 
            XWPFTableCell source, Object data,
            Map<String, Map<String, String>> dicsMap) {
        // 列属性
        if (source.getCTTc() != null) {
            target.getCTTc().setTcPr((CTTcPr) source.getCTTc().getTcPr().copy());
        }
        // 删除段落
        for (int pos = 0; pos < target.getParagraphs().size(); pos++) {
            target.removeParagraph(pos);
        }
        // 添加段落
        for (XWPFParagraph sourceP : source.getParagraphs()) {
            XWPFParagraph targetP = target.addParagraph();
            copyParagraph(targetP, sourceP, data, dicsMap);
        }
    }

    /**
     * 功能描述:复制段落，从source到target
     */
    private static void copyParagraph(
            XWPFParagraph target, 
            XWPFParagraph source, 
            Object data, 
            Map<String, Map<String, String>> dicsMap) {
        // 设置段落样式
        target.getCTP().setPPr((CTPPr) source.getCTP().getPPr().copy());
        // 移除所有的run
        for (int pos = target.getRuns().size() - 1; pos >= 0; pos--) {
            target.removeRun(pos);
        }
        
        // copy 新的run
        for (XWPFRun sourceRun : source.getRuns()) {
            XWPFRun targetRun = target.createRun();
            copyRun(targetRun, sourceRun, data, dicsMap);
        }
    }

    /**
     * 功能描述:复制RUN，从source到target
     */
    private static void copyRun(
            XWPFRun target, 
            XWPFRun source, 
            Object data, 
            Map<String, Map<String, String>> dicsMap) {
        // 设置run属性
        target.getCTR().setRPr((CTRPr) source.getCTR().getRPr().copy());
        
        String sourceText = source.toString();
        if (hasBookmark(sourceText)) {
            target.setText(parseValue(sourceText, data, dicsMap), 0);
        } else {
            target.setText(sourceText, 0);
        }
    }
    
    /**
     * 标签解释
     */
    private static String parseValue(String text, Object object, Map<String, Map<String, String>> dicsMap) {
        Matcher matcher = bookmarkMatcher(text);
        StringBuffer buffer = new StringBuffer();
        while (matcher.find()) {
            String value = parseBookmark(matcher.group(1), object, dicsMap);
            matcher.appendReplacement(buffer, value == null ? "" : value);
        }
        matcher.appendTail(buffer);
        return buffer.toString();
    }
    
    /**
     * 标签解释
     */
    private static String parseBookmark(
            String bookmark, 
            Object object, 
            Map<String, Map<String, String>> dicsMap) {
        if (bookmark.indexOf("prechecklist") == 0) {
            // ${prechecklist|dicCodeField|dicCodeValueField}
            String[] dicts = bookmark.split("\\|");
            String dicCode = String.valueOf(BeanUtil.getValue(object, dicts[1]));
            String dicCodeValue = String.valueOf(BeanUtil.getValue(object, dicts[2]));
            if (!NON_VALUE.equals(dicCodeValue) && dicCode != null && !"null".equals(dicCode)) {
                Map<String, String> dicMap = dicsMap.get(dicCode);
                if (dicMap != null) {
                    StringBuffer buffer = new StringBuffer();
                    dicMap.forEach((dicKey, dicValue) -> {
                        if (dicCodeValue != null && dicCodeValue.equals(dicKey)) {
                            buffer.append("  🗹").append(dicValue);
                        } else {
                            buffer.append("  ☐").append(dicValue);
                        }
                    });
                    return buffer.length() > 2 ? buffer.substring(2) : "";
                }
            }
        } else if (bookmark.indexOf("checklist") == 0) {
            // ${checklist|dicCode|dicCodeValueField}
            String[] dicts = bookmark.split("\\|");
            String dicCode = dicts[1];
            String dicCodeValue = String.valueOf(BeanUtil.getValue(object, dicts[2]));
            if (!NON_VALUE.equals(dicCodeValue)
                    && dicCode != null
                    && !"null".equals(dicCode)) {
                Map<String, String> dicMap = dicsMap.get(dicCode);
                if (dicMap != null) {
                    StringBuffer buffer = new StringBuffer();
                    dicMap.forEach((dicKey, dicValue) -> {
                        if (dicCodeValue != null && dicCodeValue.equals(dicKey)) {
                            buffer.append("  🗹").append(dicValue);
                        } else {
                            buffer.append("  ☐").append(dicValue);
                        }
                    });
                    return buffer.length() > 2 ? buffer.substring(2) : "";
                }
            }
        } else if (bookmark.indexOf("dict") == 0) {
            // ${dict|dicCode|dicCodeValueField}
            String[] dicts = bookmark.split("\\|");
            String dicCode = dicts[1];
            String dicCodeValue = String.valueOf(BeanUtil.getValue(object, dicts[2]));
            if (dicCode != null && !"null".equals(dicCode)) {
                Map<String, String> dicMap = dicsMap.get(dicCode);
                if (dicMap != null) {
                    return dicMap.get(dicCodeValue);
                }
            }
        } else {
            String format = "yyyy-MM-dd HH:mm:ss";
            if (bookmark.contains("|")) {
                String[] bookmarks = bookmark.split("\\|");
                bookmark = bookmarks[0];
                format = bookmarks[1];
            }
            Object propertyValue = BeanUtil.getValue(object, bookmark);
            if (propertyValue != null) {
                if (propertyValue instanceof Date) {
                    return DateUtil.format((Date) propertyValue, format).replace(" 00:00:00", "");
                } else {
                    String value = propertyValue.toString();
                    if (value.endsWith(".0")) {
                        value = value.substring(0, value.length() - 2);
                    } else if (value.endsWith(" 00:00:00")) {
                        value = value.replace(" 00:00:00", "");
                    }
                    return value;
                }
            }
        }
        
        return "";
    }
    
    /**
     * 替换所有的标签
     */
    public static void replaceSS(
            XWPFDocument docx, 
            Object object,
            Map<String, Map<String, String>> dicsMap) {
        // 替换段落里面的变量
        replaceInParas(docx, object, dicsMap);
        
        // 替换表格里面的变量
        replaceInTable(docx, object, dicsMap);
    }

    /**
     * 替换段落里面的变量
     */
    public static void replaceInParas(
            XWPFDocument doc, 
            Object object,
            Map<String, Map<String, String>> dicsMap) {
        Iterator<XWPFParagraph> iterator = doc.getParagraphsIterator();
        while (iterator.hasNext()) {
            replaceInPara(iterator.next(), object, dicsMap);
        }
    }

    /**
     * 替换表格里面的变量
     * 
     * @param doc 要替换的文档
     */
    public static void replaceInTable(
            XWPFDocument doc, 
            Object object,
            Map<String, Map<String, String>> dicsMap) {
        Iterator<XWPFTable> iterator = doc.getTablesIterator();
        while (iterator.hasNext()) {
            for (XWPFTableRow row : iterator.next().getRows()) {
                for (XWPFTableCell cell : row.getTableCells()) {
                    for (XWPFParagraph para : cell.getParagraphs()) {
                        replaceInPara(para, object, dicsMap);
                    }
                }
            }
        }
    }

    /**
     * 替换段落里面的变量
     * 
     * @param para 要替换的段落
     */
    public static void replaceInPara(
            XWPFParagraph para, 
            Object object,
            Map<String, Map<String, String>> dicsMap) {
        List<XWPFRun> runs = para.getRuns();
        for (XWPFRun run : runs) {
            String text = run.toString();
            // System.out.println(text);
            if (hasBookmark(text)) {
                run.setText(parseValue(run.toString(), object, dicsMap), 0);
            }
        }
    }
    
    /**
     * 正则是否包含标签
     */
    public static boolean hasBookmark(String str) {
        if (str == null || str.isEmpty()) {
            return false;
        }
        
        Pattern pattern = Pattern.compile("\\$\\{(.+?)}", Pattern.CASE_INSENSITIVE);
        return pattern.matcher(str).matches();
    }

    /**
     * 正则匹配字符串
     */
    public static Matcher bookmarkMatcher(String str) {
        if (str == null || str.isEmpty()) {
            return null;
        }
        
        Pattern pattern = Pattern.compile("\\$\\{(.+?)}", Pattern.CASE_INSENSITIVE);
        return pattern.matcher(str);
    }

    /**
     * 关闭输入流
     */
    private static void close(InputStream inputStream) {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                logger.error("", e);
            }
        }
    }
}
